﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using Lime49;
using Lime49.UI;
using LockCrypt.DAL;
using LockCrypt.Core;
using Microsoft.Win32;

namespace LockCrypt {
    /// <summary>
    /// Window for entering a database password.
    /// </summary>
    public partial class WinPromptPassword : Window {
        private OpenFileDialog dlgOpen;
        private BackgroundWorker worker;

        public IDbProvider DatabaseProvider {
            get { return dbSelector.Provider; }
            set { dbSelector.Provider = value; }
        }

        /*public static DependencyProperty FileNameProperty = DependencyProperty.Register("FileName", typeof(string), typeof(WinPromptPassword), new PropertyMetadata(string.Empty));
        /// <summary>
        /// Gets the name of the selected file.
        /// </summary>
        /// <value>The name of the selected file.</value>
        public string FileName {
            get { return System.Convert.ToString(base.GetValue(FileNameProperty)); }
            set { base.SetValue(FileNameProperty, value); }
        }*/

        public static DependencyProperty CanEditCredentialsProperty = DependencyProperty.Register("CanEditCredentials", typeof(bool), typeof(WinPromptPassword), new PropertyMetadata(false));
        /// <summary>
        /// Gets or sets a value whether the file name can be modified.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the file name can be modified, otherwise <c>false</c>.
        /// </value>
        public bool CanEditCredentials {
            get { return System.Convert.ToBoolean(base.GetValue(CanEditCredentialsProperty)); }
            set { base.SetValue(CanEditCredentialsProperty, value); }
        }

        public static DependencyProperty EncryptionAlgorithmProperty = DependencyProperty.Register("EncryptionAlgorithm", typeof(EncryptionProviders), typeof(WinPromptPassword), new PropertyMetadata(EncryptionProviders.None));
        /// <summary>
        /// Gets the selected encryption algorithm.
        /// </summary>
        /// <value>The selected encryption algorithm.</value>
        public EncryptionProviders EncryptionAlgorithm {
            get { return (EncryptionProviders)base.GetValue(EncryptionAlgorithmProperty); }
            set { base.SetValue(EncryptionAlgorithmProperty, value); }
        }

        /// <summary>
        /// Gets the password entered.
        /// </summary>
        /// <value>The password entered.</value>
        public string Password {
            get { return txtPassword.Password; }
            set { txtPassword.Password = value; }
        }

        /// <summary>
        /// Initializes a new <see cref="WinPromptPassword"/>.
        /// </summary>
        public WinPromptPassword() {
            InitializeComponent();
            cboEncryption.ItemsSource = EnumBinder.EnumHelper.ToList<string>(typeof(EncryptionProviders));
            dlgOpen = new OpenFileDialog();
            dlgOpen.CheckPathExists = true;
            dlgOpen.Title = I18nUtils.GetString("Strings", "ChooseFile");
            dlgOpen.Multiselect = false;
            EncryptionAlgorithm = EncryptionProviders.None;
        }

        /// <summary>
        /// Initializes a new <see cref="WinPromptPassword"/>.
        /// </summary>
        /// <param name="provider">The database provider.</param>
        public WinPromptPassword(IDbProvider provider) 
            :this() {
            DatabaseProvider = provider;
            dbSelector.Provider = provider;
            EncryptionAlgorithm = provider.Encryption.EncryptionAlgorithm;
            if(provider.DatabaseType == Constants.DatabaseType.Legacy) {
                cboEncryption.IsEnabled = false;
            }
        }

        /// <summary>
        /// Loads available parent groups when the Window loads.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            StringBuilder filterBuffer = new StringBuilder(string.Format("{0}|*.*", I18nUtils.GetString("Strings", "AllFiles")));
            switch(DatabaseProvider.DatabaseType) {
                case Constants.DatabaseType.Legacy:
                    filterBuffer.AppendFormat("|{0} (*.lce)|*.lce", I18nUtils.GetString("Strings", "LegacyDatabase"));
                    dlgOpen.DefaultExt = ".lce";
                    break;
                case Constants.DatabaseType.SQLite:
                    filterBuffer.AppendFormat("|{0}|*.db3;*.lce", I18nUtils.GetString("Strings", "SQLiteDatabases"));
                    dlgOpen.DefaultExt = ".db3";
                    break;
                case Constants.DatabaseType.XML:
                    filterBuffer.AppendFormat("|{0} (*.xml)|*.xml", I18nUtils.GetString("Strings", "XmlFiles"));
                    filterBuffer.AppendFormat("|{0} (*.lce)|*.lce", I18nUtils.GetString("Strings", "EncryptedXmlFile"));
                    if(EncryptionAlgorithm == EncryptionProviders.None) {
                        dlgOpen.DefaultExt = ".xml";
                        dlgOpen.FilterIndex = 2;
                    } else {
                        dlgOpen.DefaultExt = ".lce";
                        dlgOpen.FilterIndex = 3;
                    }
                    break;
            }
            dlgOpen.Filter = filterBuffer.ToString();
            cboEncryption.SelectedValue = DatabaseProvider.Encryption.EncryptionAlgorithm;
        }

        /*// <summary>
        /// Shows a dialog to browse for an input file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void BrowseInputFile(object sender, ExecutedRoutedEventArgs e) {
            try {
                if(File.Exists(this.FileName)) {
                    dlgOpen.FileName = this.FileName;
                }
            } catch { }
            if(dlgOpen.ShowDialog() == true) {
                txtFilename.Text = dlgOpen.FileNames[0];
                txtFilename.IsEnabled = true;
            }
        }*/

        /// <summary>
        /// Checks the data entered is valid.
        /// </summary>
        /// <returns><c>True</c> if the data is valid, otherwise <c>false</c>.</returns>
        private bool Validate() {
            bool valid = true;
            if(!dbSelector.ValidateSettings(false)) {
                valid = false;
            } else {
                if(DatabaseProvider.Encryption.EncryptionAlgorithm != EncryptionAlgorithm) {
                    DatabaseProvider = DalConfigurationManager.GetProvider(DatabaseProvider.DatabaseType, this.EncryptionAlgorithm, DatabaseProvider.Encryption.EncryptionKey, DatabaseProvider.ConnectionString);
                }
                dbSelector.EncryptionPassword = this.Password;
                
                WaitForWorker();
                IDbProvider providerToCheck = DatabaseProvider; // can't access from worker thread
                worker = new BackgroundWorker();
                worker.DoWork += (s, dwe) => {
                    dwe.Result = providerToCheck.CheckPassword();
                };
                worker.RunWorkerCompleted += (s, rwe) => {
                    SetBusy(false);
                    if(rwe.Error != null) {
                        if(rwe.Error is LockCryptException && (rwe.Error as LockCryptException).ErrorCode == 236) {
                            DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "InvalidDatabase"), I18nUtils.GetString("Errors", "Error"));
                        } else {
                            DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                        }
                        valid = false;
                    } else if(!Convert.ToBoolean(rwe.Result)) {
                        DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "IncorrectPassword"), I18nUtils.GetString("Errors", "Error"));
                        txtPassword.Focus();
                        valid = false;
                    }
                };
                SetBusy(true);
                worker.RunWorkerAsync();
                WaitForWorker();
            }
            return valid;
        }

        /// <summary>
        /// Validates the password then returns a dialog result of true.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Open(object sender, ExecutedRoutedEventArgs e) {
            if(Validate()) {
                DialogResult = true;
            }
        }

        /// <summary>
        /// Closes the window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            DialogResult = false;
        }

        /// <summary>
        /// Sets the progress bar to visible or collapsed
        /// </summary>
        /// <param name="isBusy"><c>true</c> if the dialog is busy, otherwise <c>false</c>.</param>
        private void SetBusy(bool isBusy) {
            if(isBusy) {
                prgStatus.Visibility = Visibility.Visible;
                lblStatus.Visibility = Visibility.Visible;
            } else {
                prgStatus.Visibility = Visibility.Collapsed;
                lblStatus.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// Determines whether the BackgroundWorker is busy.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void IsWorkerBusy(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = IsLoaded && (worker == null || !worker.IsBusy);
        }

        /// <summary>
        /// Waits for the BackgroundWorker to complete before returning.
        /// </summary>
        private void WaitForWorker() {
            if(worker != null && worker.IsBusy) {
                while(worker.IsBusy) {
                    Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate() { }));
                }
            }
        }
    }
}
